GraphQL

http://graphql.org/

GraphQL brings the power of the data query all the way down to the component level.

Most data access requires multiple repetitve layers that slightly transform data:

  • Storage (Sql or NoSql Data Store)
  • Server (Rest or Ad-hoc Web Api)
  • Client Side Http Requests (Ajax, RxJS, etc.)
  • Client State (Angular Services, React Redux, React Mobx, etc.)
  • Components

In the above architecture, a query about a single object must be implemented at each level. Also, common patterns like filtering and paging must be handled at each level as well.

To make a change on the UI that requires additional data about an object, each layer must be modified to provide that additional data. Not only is this tedius work, it also affects the entire code base and could easily introduce bugs requiring testing at each level.

GraphQL Solves Client Data Access

GraphQL solves that problem by allowing the client to define the structure of the query. And even better, each component can define it’s own data needs.

  • Component Data Query
  • Component

Sample

Here is a sample using React-Apollo (ES6):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function TodoApp({ data: { todos, refetch } }) {
return (
<div>
<button onClick={() => refetch()}>
Refresh
</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
</div>
);
}
export default graphql(gql`
query TodoAppQuery {
todos {
id
text
}
}
`)(TodoApp);

The actual GraphQL Query is here:

1
2
3
4
5
6
query TodoAppQuery {
todos {
id
text
}
}

In this example, the query is requesting todos with the id and text of each.

Whenever, it is time to display this component, that query is automatically requested and then injected into the React component’s data object.

Without getting into the details of how that works, whenever something triggers the UI view to change, all the new queries will be combined together into a main query that is then sent to the server endpoint.

What is even better, most client side implementations automatically cache the requests and any data already availabe in the cache will not be requested from the server a second time (of course this can be controlled in case a fresh copy is needed).

So How Does the Server Provide the Data?

On the server side, the server has a single GraphQL endpoint that will receive all requests. It is the responsibility of the server to parse the request and give only the requested data back to the client.

Of course, the standard libraries handle the parsing and the developer has only one requirement:

Resolve the data request for each data type:

1
2
3
4
5
6
7
8
9
10
11
12
const resolverMap = {
Query: {
author(obj, args, context, info) {
return getAuthorById(args.id);
},
},
Author: {
posts(author) {
return getPostsByAuthorId(author.id);
},
},
};

The advantage of this, is that the developer can focus on a single data type at once without worrying about nested types or deciding how deeply nested the response data needs to be.

In the example above, each has a specific purpose: return a single author or return the posts that belong to an author:

1
2
3
4
5
...
getAuthorById(args.id)
...
getPostsByAuthorId(author.id)
...

The getAuthorById doesn’t have to worry about whether to return posts or what data about the nested posts might be needed by the client. It just returns the author data only. Likewise, the getPostsByAuthorId has a very clean purpose and doesn’t have to worry about nested objects.

By returning on the object and the entire object, the framework can automatically prune unnecessary data and combine the multiple objects into the requested object graph which was requested by the client.

Also, the GraphQL libraries support Promises and so those resolver methods can use async/await and have a very clean implementation.

The end result, is that the GraphQL library will wait for all the promises, combine all the data, prune extra data, and return exactly what was requested in a single network response.

So How Does the Client Change the Data?

Another cool part of GraphQL is how data changes. In GraphQL, a data change is called a mutation.

The nice thing about a mutation is that it supports Optimistic UI. This means that the ui data is modified locally on the client while being processed on the server. This allows the UI to update with a preview of the data change while waiting for the server response. Then when the server sends the actual response, it can replace the temporary optimistic data. This is one of the benefits of a mutation compared to a simple Rest Post.

On the server side these mutations must be implemented in the same way as the resolvers:

1
2
3
4
5
6
7
8
9
10
const resolverMap = {
...
Mutation: {
addAuthor(_, { firstName, lastName }) {
...
return author;
},
},
...
};

The differences with a resolve query are easily seen below:

1
2
3
4
5
6
7
8
9
...
Query: {
// Get an author (args contains the author id)
author(obj, args: { id }, context, info)...
...
Mutation: {
// Create an author (args )
addAuthor(_, args: { firstName, lastName })...

Really, the only difference is the Query and Mutation keyword. They both use the 2nd parameter as the args and use those args to either get data or modify it.

This Looks Cool, Too Bad I Can’t Use it

That’s where it get’s interesting. It is possible to use the parser on the client side and basically wrap a Rest Api with a GraphQL schema which you can use in your client side code.

Then, the next step is to move this to the server and slowly replace the REST calls with direct calls to the necessary resources.

This provides an adoption path where it can be used in client side web apps today.

This video gives a good overview of that process:

https://www.youtube.com/watch?v=UBGzsb2UkeY

Notes

I found two implementations of the GraphQL:

I also found a VSCode extension that provides all the cool editor features we love:

Zero to GraphQL in 30 Minutes